#include "components/shell.h"
#include "heap.h"
#include "sys_log.h"

#define _HEAP_HEAP "Execute about memory optreate"
#define _HELP_LIST "Print the memory status"
#define _HELP_LIST_SYS "Print the global memory status"
#define _HELP_LIST_OBJ "Print the object memory list"
#define _HELP_ALLOC "Allocate a block of memory for the object\r\n\tUsage: alloc <name> <size>"
#define _HELP_FREE "Free a block of memory\r\n\tUsage: free <name>"
#define _HELP_RESET "Free all block of memory"
#define _HELP_READ "Read and print object memory data\r\n\tUsage: read <name> [offset] [size]"
#define _HELP_WRITE_STR "Write string to object memory\r\n\tUsage: write <name> <offset> <string>"
#define _HELP_WRITE_VALUE "Write a value to object memory\r\n\tUsage: write <name> <offset> <value>"

static heapk_t s_heapk_hdl;

SHELL_CMD_FN(_list_sys);
SHELL_CMD_FN(_list_obj);
SHELL_CMD_FN(_alloc);
SHELL_CMD_FN(_free);
SHELL_CMD_FN(_reset);
SHELL_CMD_FN(_read);
SHELL_CMD_FN(_write_string);
SHELL_CMD_FN(_write_value);

SHELL_CMD_CP_FN(_exist_obj);

SH_DEF_SUB_CMD(
    _cmd_list_sublist,
    SH_SETUP_CMD("sys", _HELP_LIST_SYS, _list_sys, NULL), //
    SH_SETUP_CMD("obj", _HELP_LIST_OBJ, _list_obj, NULL), //
);

SH_DEF_SUB_CMD(
    _cmd_heap_sublist,
    SH_SETUP_CMD("list",        _HELP_LIST,        NULL,          _cmd_list_sublist), //
    SH_SETUP_CMD("alloc",       _HELP_ALLOC,       _alloc,        NULL),              //
    SH_SETUP_CMD("free",        _HELP_FREE,        _free,         _exist_obj),        //
    SH_SETUP_CMD("reset",       _HELP_RESET,       _reset,        NULL),              //
    SH_SETUP_CMD("read",        _HELP_READ,        _read,         _exist_obj),        //
    SH_SETUP_CMD("write-str",   _HELP_WRITE_STR,   _write_string, _exist_obj),        //
    SH_SETUP_CMD("write-value", _HELP_WRITE_VALUE, _write_value,  _exist_obj),        //
);

SHELL_REGISTER_CMD(
    register_heap_command,
    SHELL_SETUP_CMD("heap", _HEAP_HEAP, NULL, _cmd_heap_sublist), //
);

typedef struct
{
    void *mem;
    uint size;
} mem_state_t;

static heapk_t *_get_hdl(sh_t *sh_hdl);
static mem_state_t _get_mem(sh_t *sh_hdl, const char *key_str);

SHELL_CMD_FN(_list_sys)
{
#define _MSIZE_KB(N) (N) / 0x400, (N) % 0x400 * 10 / 0x400
#define _MSIZE_MB(N) (N) / 0x100000, (N) % 0x100000 * 10 / 0x100000

    unsigned used_size = heap_used_size(NULL);      // 获取总已使用空间
    unsigned free_size = heap_free_size(NULL);      // 获取总空闲空间（包含所有碎片）
    unsigned block_max = heap_block_max(NULL);      // 获取当前最大的连续空间
    unsigned fragment_size = free_size - block_max; // 总碎片空间
    shell_print(sh_hdl, "heap global status:\r\n");
    shell_print(sh_hdl, "  handle: %p\r\n", NULL);
    shell_print(sh_hdl, "  begin:  %p\r\n", heap_get_base(NULL));
    shell_print(sh_hdl, "  end:    %p\r\n", heap_get_end(NULL));

    if (used_size < 0x400)
        shell_print(sh_hdl, "  used:   %u\r\n", used_size);
    else if (used_size < 0x100000)
        shell_print(sh_hdl, "  used:   %u.%u KiB\r\n", _MSIZE_KB(used_size));
    else
        shell_print(sh_hdl, "  used:   %u.%u MiB\r\n", _MSIZE_MB(used_size));

    if (fragment_size < 0x400)
        shell_print(sh_hdl, "  free fragment:  %u B\r\n", fragment_size);
    else if (fragment_size < 0x100000)
        shell_print(sh_hdl, "  free fragment:  %u.%u KiB\r\n", fragment_size / 0x400, (fragment_size % 0x400 * 10 + 0x400 - 1) / 0x400);
    else
        shell_print(sh_hdl, "  free fragment:  %u.%u MiB\r\n", fragment_size / 0x100000, (fragment_size % 0x100000 * 10 + 0x100000 - 1) / 0x100000);

    if (block_max < 0x400)
        shell_print(sh_hdl, "  free max block: %u B\r\n", block_max);
    else if (block_max < 0x100000)
        shell_print(sh_hdl, "  free max block: %u.%u KiB\r\n", _MSIZE_KB(block_max));
    else
        shell_print(sh_hdl, "  free max block: %u.%u MiB\r\n", _MSIZE_MB(block_max));

    if (free_size < 0x400)
        shell_print(sh_hdl, "  free total:     %u B\r\n", free_size);
    else if (free_size < 0x100000)
        shell_print(sh_hdl, "  free total:     %u.%u KiB\r\n", _MSIZE_KB(free_size));
    else
        shell_print(sh_hdl, "  free total:     %u.%u MiB\r\n", _MSIZE_MB(free_size));

    return 0;
}

SHELL_CMD_FN(_list_obj)
{
    uint len_max = 3;
    uint count = 0;
    void *mem_base;
    void *key_base;
    uint8_t key_size;

    heap_entry_critical();
    if (s_heapk_hdl.handle)
    {
        heapk_list_reset(&s_heapk_hdl);
        while (heapk_list_next(&s_heapk_hdl, &mem_base, &key_base, &key_size) != false)
        {
            count++;
            if (len_max < key_size)
                len_max = key_size;
        }
    }

    shell_print(sh_hdl, "%-*s         base     size\r\n", len_max, "obj");
    for (uint i = 0; i < 22 + len_max; i++)
    {
        shell_print(sh_hdl, "-");
    }
    shell_print(sh_hdl, "\r\n");
    if (count)
    {
        heapk_list_reset(&s_heapk_hdl);
        while (heapk_list_next(&s_heapk_hdl, &mem_base, &key_base, &key_size) != false)
        {
            char key[0x100];
            memcpy(key, key_base, key_size);
            key[key_size] = '\0';
            shell_print(sh_hdl, "%-*s   %p %8u\r\n", len_max, key, mem_base, heapk_block_size(mem_base));
        }
    }

    heap_exit_critical();
    for (uint i = 0; i < 22 + len_max; i++)
    {
        shell_print(sh_hdl, "-");
    }
    shell_print(sh_hdl, "\r\n");

    return 0;
}

SHELL_CMD_FN(_alloc)
{
    if (argc >= 2)
    {
        heapk_t *heapk_hdl = _get_hdl(sh_hdl);
        if (heapk_hdl == NULL)
        {
            return -1;
        }

        sh_parse_t parse = sh_parse_value(argv[1]);
        if (parse.type != _PARSE_TYPE_UNSIGNED)
        {
            shell_print(sh_hdl, "param: '%s' is not unsigned integer\r\n", argv[1]);
            return -1;
        }

        const void *key = argv[0];
        int size = parse.value.val_integer;
        void *mem = heapk_malloc(heapk_hdl, size, key, strlen(key));

        if (mem == NULL)
        {
            shell_print(sh_hdl, "operate fail\r\n");
            return -1;
        }

        memset(mem, 0, size);

        return 0;
    }
    else
    {
        shell_print(sh_hdl, _HELP_ALLOC "\r\n");
        return -1;
    }
}

SHELL_CMD_FN(_free)
{
    if (argc >= 1)
    {
        heapk_t *heapk_hdl = _get_hdl(sh_hdl);
        if (heapk_hdl == NULL)
        {
            return -1;
        }

        mem_state_t state = _get_mem(sh_hdl, argv[0]);
        if (state.mem)
        {
            heapk_free(state.mem);
            return 0;
        }
        else
        {
            return -1;
        }
    }
    else
    {
        shell_print(sh_hdl, _HELP_FREE "\r\n");
        return -1;
    }
}

SHELL_CMD_FN(_reset)
{
    heapk_t *heapk_hdl = _get_hdl(sh_hdl);
    if (heapk_hdl == NULL)
    {
        return -1;
    }

    heapk_delete(heapk_hdl);
    s_heapk_hdl.handle = NULL;
    return 0;
}

SHELL_CMD_FN(_read)
{
    if (argc >= 1)
    {
        mem_state_t state = _get_mem(sh_hdl, argv[0]);
        struct
        {
            void *mem;
            uint offset;
            uint size;
        } result;

        if (state.mem == NULL)
        {
            return -1;
        }

        sh_parse_t parse1;
        parse1.value.val_unsigned = 0;
        if (argc >= 2)
        {
            parse1 = sh_parse_value(argv[1]);
            if (parse1.type != _PARSE_TYPE_UNSIGNED)
            {
                shell_print(sh_hdl, "param: '%s' is not unsigned integer\r\n", argv[1]);
                return -1;
            }
        }

        sh_parse_t parse2;
        parse2.value.val_unsigned = state.size - parse1.value.val_unsigned;
        if (argc >= 3)
        {
            parse2 = sh_parse_value(argv[2]);
            if (parse2.type != _PARSE_TYPE_UNSIGNED)
            {
                shell_print(sh_hdl, "param: '%s' is not unsigned integer\r\n", argv[2]);
                return -1;
            }
        }

        if (parse1.value.val_unsigned + parse2.value.val_unsigned > state.size)
        {
            shell_print(sh_hdl, "Obj '%s' size = %d, out of range\r\n", argv[0], state.size, argv[2]);
            return -1;
        }

        result.offset = parse1.value.val_unsigned;
        result.size = parse2.value.val_unsigned;
        result.mem = state.mem;
        _sys_log_dump(result.offset, &((u8_t *)result.mem)[result.offset], result.size, 1, 0);
        return 0;
    }
    else
    {
        shell_print(sh_hdl, _HELP_READ "\r\n");
        return -1;
    }
}

SHELL_CMD_FN(_write_string)
{
    if (argc >= 3)
    {
        mem_state_t state = _get_mem(sh_hdl, argv[0]);
        struct
        {
            void *mem;
            uint offset;
            uint size;
        } result;
        const void *src;

        if (state.mem == NULL)
        {
            return -1;
        }

        sh_parse_t parse1 = sh_parse_value(argv[1]);
        if (parse1.type != _PARSE_TYPE_UNSIGNED)
        {
            shell_print(sh_hdl, "param: '%s' is not unsigned integer\r\n", argv[1]);
            return -1;
        }
        result.mem = &((u8_t *)state.mem)[parse1.value.val_unsigned];
        result.offset = parse1.value.val_unsigned;
        result.size = strlen(argv[2]) + 1;
        src = argv[2];

        if (result.offset + result.size > state.size)
        {
            shell_print(sh_hdl, "Obj '%s' size = %d, out of range\r\n", argv[0], state.size, argv[2]);
            return -1;
        }
        else
        {
            memcpy(result.mem, src, result.size);
            return 0;
        }
    }
    else
    {
        shell_print(sh_hdl, _HELP_WRITE_STR "\r\n");
        return -1;
    }
}

SHELL_CMD_FN(_write_value)
{
    if (argc >= 3)
    {
        mem_state_t state = _get_mem(sh_hdl, argv[0]);
        struct
        {
            void *mem;
            uint offset;
            uint size;
        } result;
        const void *src;

        if (state.mem == NULL)
        {
            return -1;
        }

        sh_parse_t parse1 = sh_parse_value(argv[1]);
        if (parse1.type != _PARSE_TYPE_UNSIGNED)
        {
            shell_print(sh_hdl, "param: '%s' is not unsigned integer\r\n", argv[1]);
            return -1;
        }
        result.mem = &((u8_t *)state.mem)[parse1.value.val_unsigned];
        result.offset = parse1.value.val_unsigned;

        sh_parse_t parse2 = sh_parse_value(argv[2]);
        switch (parse2.type)
        {
        case _PARSE_TYPE_INTEGER:  // 带符号整型
        case _PARSE_TYPE_UNSIGNED: // 无符号整型
        {
            if (parse2.value.val_unsigned < 0x100)
                result.size = 1;
            else if (parse2.value.val_unsigned < 0x10000)
                result.size = 2;
            else
                result.size = sizeof(int);

            src = &parse2.value.val_unsigned;
            break;
        }
        case _PARSE_TYPE_FLOAT: // 浮点
        {
            result.size = sizeof(parse2.value.val_float);
            src = &parse2.value.val_unsigned;
            break;
        }
        default:
            shell_print(sh_hdl, "param: '%s' is not value\r\n", argv[1]);
            return -1;
        }

        if (result.offset + result.size > state.size)
        {
            shell_print(sh_hdl, "Obj '%s' size = %d, out of range\r\n", argv[0], state.size, argv[2]);
            return -1;
        }
        else
        {
            memcpy(result.mem, src, result.size);
            return 0;
        }
    }
    else
    {
        shell_print(sh_hdl, _HELP_WRITE_VALUE "\r\n");
        return -1;
    }
}

SHELL_CMD_CP_FN(_exist_obj)
{
    void *mem_base;
    void *key_base;
    uint8_t key_size;

    if (argc + flag == 1)
    {
        heap_entry_critical();
        if (s_heapk_hdl.handle)
        {
            heapk_list_reset(&s_heapk_hdl);
            while (heapk_list_next(&s_heapk_hdl, &mem_base, &key_base, &key_size) != false)
            {
                char key[0x101];
                memcpy(key, key_base, key_size);
                key[key_size++] = ' ';
                key[key_size] = '\0';
                sh_completion_resource(sh_hdl, NULL, key, NULL);
            }
        }
        heap_exit_critical();
    }
}

static heapk_t *_get_hdl(sh_t *sh_hdl)
{
    if (s_heapk_hdl.handle == NULL)
    {
        if (heapk_create(&s_heapk_hdl, NULL, 20) != 0)
        {
            shell_print(sh_hdl, "Error: create obj memory fail!\r\n");
            return NULL;
        }
    }
    return &s_heapk_hdl;
}

static mem_state_t _get_mem(sh_t *sh_hdl, const char *key_str)
{
    heapk_t *heapk_hdl = _get_hdl(sh_hdl);
    mem_state_t result;

    memset(&result, 0, sizeof(result));

    if (heapk_hdl)
    {
        result.mem = heapk_block_base(heapk_hdl, key_str, strlen(key_str));
        if (result.mem)
        {
            result.size = heapk_block_size(result.mem);
        }
        else
        {
            shell_print(sh_hdl, "Object '%s' not found\r\n", key_str);
        }
    }

    return result;
}

/*
测试命令
clear
select heap
reset
alloc m1 1
alloc m2 2
alloc abcde 10
read abcde
list obj
write-str abcde 0 "a\tc"
write-value abcde 4 0x12345678
read abcde
sh history
*/
